package io.leo.leodata.db;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.security.auth.Subject;

import io.leo.leoutils.Property;
import io.leo.leoutils.ReflectUtils;
import io.leo.leoutils.StringUtils;

/**
 * 以下使用泛型技术提供一个Dao模板，实现基本的增删改查功能
 *
 * 我认为，数据库中的表名应该和对应的bean类名一致，而数据库中的表遵循下划线小写命名法，bean类应该支持驼峰命名法，二者是一一对应的。
 *
 * 通过对应的getter和setter方法可以访问到bean的属性，并通过对应的下划线命名法去数据库中查找
 * @param <Bean>
 */
public abstract class DaoTemplate<Bean> {

    private Field primaryKey;
    private List<Field> otherFields;
    //表名
    protected String tableName;
    //真实类型
    protected Class realType;


    private SQLiteOpenHelper helper;
    public DaoTemplate(SQLiteOpenHelper helper){
        this.helper = helper;
        this.realType = ReflectUtils.getActualType(this.getClass());
        this.tableName = realType.getSimpleName();

        otherFields = new ArrayList<>();
        ReflectUtils.getProperties(realType).read(new ReflectUtils.PropertyReadCallback() {
            @Override
            public void onRead(String name, Method getter, Method setter) {
                Field field;
                try {
                    field = realType.getDeclaredField(name);
                    Id id  = field.getAnnotation(Id.class);
                    if (id!=null)
                        primaryKey = field;
                    else
                        otherFields.add(field);
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                }
            }
        });
    }
    /**
     * 查询
     * @param sql
     * @param args
     * @return
     */
    public List<Bean> query(String sql,Object...args){
        SQLiteDatabase db = helper.getReadableDatabase();
        //rawQuery只提供字符串参数替换，所以自己写一个填充sql语句的
        sql = fillArgs(sql,args);
        final Cursor cursor = db.rawQuery(sql,null);
        List<Bean> beans = new ArrayList<>();
        while (cursor.moveToNext()){
            try {
                final Object bean = realType.newInstance();
                Property property = ReflectUtils.getProperties(bean.getClass());
                property.read(new ReflectUtils.PropertyReadCallback() {
                    @Override
                    public void onRead(String name, Method getter, Method setter) {
                        int columnIndex = cursor.getColumnIndex(ReflectUtils.camelCaseToUnderLine(name));
                        int columnType = cursor.getType(columnIndex);
                        Object obj = null;
                        switch (columnType){
                            case Cursor.FIELD_TYPE_STRING:
                                obj=cursor.getString(columnIndex);
                                break;
                            case Cursor.FIELD_TYPE_INTEGER:
                                obj = cursor.getLong(columnIndex);
                                break;
                            case Cursor.FIELD_TYPE_FLOAT:
                                obj = cursor.getDouble(columnIndex);
                                break;
                            case Cursor.FIELD_TYPE_BLOB:
                                obj = cursor.getBlob(columnIndex);
                                break;
                            default:
                                obj = null;
                        }
                        try {
                            Class setterParamClz = setter.getParameterTypes()[0];
                            if (obj==null)
                                return;
                            else if (StringUtils.in(setterParamClz.getSimpleName(),"int","Integer"))
                                setter.invoke(bean,Integer.parseInt(String.valueOf(obj)));
                            else if (StringUtils.in(setterParamClz.getSimpleName(),"short","Short"))
                                setter.invoke(bean,Short.parseShort(String.valueOf(obj)));
                            else if (StringUtils.in(setterParamClz.getSimpleName(),"byte","Byte"))
                                setter.invoke(bean,Byte.parseByte(String.valueOf(obj)));
                            else if (StringUtils.in(setterParamClz.getSimpleName(),"float","Float"))
                                setter.invoke(bean,Float.parseFloat(String.valueOf(obj)));
                            else if (StringUtils.in(setterParamClz.getSimpleName(),"StringBuffer"))
                                setter.invoke(bean,new StringBuffer(String.valueOf(obj)));
                            else if (StringUtils.in(setterParamClz.getSimpleName(),"Date"))
                                setter.invoke(bean,new Date((Long) obj));
                            else if (StringUtils.in(setterParamClz.getSimpleName(),"boolean","Boolean"))
                                setter.invoke(bean,Integer.parseInt(String.valueOf(obj))!=0?true:false);
                            else
                                setter.invoke(bean,obj);
                        } catch (Exception e){
                            throw new DaoException(e);
                        }
                    }
                });
                beans.add((Bean) bean);
            } catch (Exception e) {
                throw new DaoException(e);
            }
        }
        cursor.close();
        return beans;
    }

    public Bean getOne(Integer id){return getOne(Long.parseLong(String.valueOf(id)));}
    public Bean getOne(Long id) {
        List<Bean> queryResult = getAllWhere(ReflectUtils.camelCaseToUnderLine(primaryKey.getName()) + "=" + id);
        if (queryResult.size() == 0) return null;
        else return queryResult.get(0);
    }

    public List<Bean> getAll(){
        return query("select * from "+tableName);
    }

    public List<Bean> getAllWhere(String where){
        return query("select * from "+tableName+" where "+where);
    }

    public List<Bean> getAllOrderBy(String fields,boolean desc){
        return query("select * from "+tableName+" order by "+fields+(desc?" desc":" asc"));
    }

    /**
     * 插入
     * @return 插入后的主键返回
     */
    public long insert(Bean bean){
        SQLiteDatabase db = helper.getWritableDatabase();
        String tableName = bean.getClass().getSimpleName();
        ContentValues cv = beanToContentValues(bean);
        return db.insertOrThrow(tableName,null,cv);
    }

    //根据主键来更新数据库里的对象
    public void update(final Bean bean){
        final StringBuffer prefixSql = new StringBuffer("update "+tableName+" set ");
        final StringBuffer whereSql = new StringBuffer(" where ");

        ReflectUtils.getProperties(realType).read(new ReflectUtils.PropertyReadCallback() {
            @Override
            public void onRead(String name, Method getter, Method setter) {
                try {
                    Field field = realType.getDeclaredField(name);

                    Id id  = field.getAnnotation(Id.class);
                    if (id!=null)
                        whereSql.append(ReflectUtils.camelCaseToUnderLine(name)+"="+toStr(getter.invoke(bean)));
                    else
                        prefixSql.append(ReflectUtils.camelCaseToUnderLine(name)+"="+toStr(getter.invoke(bean))+",");
                } catch (NoSuchFieldException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        });
        //删除最后一个逗号
        prefixSql.deleteCharAt(prefixSql.length()-1);
        execSql(prefixSql.toString()+whereSql.toString());
    }



    //条件删除
    public void deleteWhere(String where){
        execSql("delete from "+ tableName + " where "+where);
    }


    /**
     * 删除一个
     * @param id id
     */
    public void deleteOne(Integer id){deleteOne(Long.parseLong(String.valueOf(id)));}
    public void deleteOne(Long id){
        deleteWhere(ReflectUtils.camelCaseToUnderLine(primaryKey.getName())+"="+id);
    }

    /**
     * 执行Sql命令
     * @param sql
     */
    public void execSql(String sql){
        SQLiteDatabase db = helper.getWritableDatabase();
        db.execSQL(sql);
    }


    private ContentValues beanToContentValues(final Bean bean){
        final ContentValues cv = new ContentValues();
        ReflectUtils.getProperties(bean.getClass()).read(new ReflectUtils.PropertyReadCallback() {
            @Override
            public void onRead(String name, Method getter, Method setter) {
                try {
                    String underlineName = ReflectUtils.camelCaseToUnderLine(name);
                    Object object = getter.invoke(bean);
                    if (object == null) return;
                    String propertyType = object.getClass().getSimpleName();
                    if (StringUtils.in(propertyType,"int","Integer"))
                        cv.put(underlineName, (Integer) object);
                    else if (StringUtils.in(propertyType,"byte","Byte"))
                        cv.put(underlineName, (Byte) object);
                    else if(StringUtils.in(propertyType,"long","Long"))
                        cv.put(underlineName, (Long) object);
                    else if (StringUtils.in(propertyType,"float","Float"))
                        cv.put(underlineName, (Float) object);
                    else if (StringUtils.in(propertyType,"short","Short"))
                        cv.put(underlineName, (Short) object);
                    else if (StringUtils.in(propertyType,"string","String"))
                        cv.put(underlineName, (String) object);
                    else if (StringUtils.in(propertyType,"double","Double"))
                        cv.put(underlineName, (Double) object);
                    else if (StringUtils.in(propertyType,"boolean","Boolean"))
                        cv.put(underlineName,((Boolean)getter.invoke(bean))?1:0);
                    else if (StringUtils.in(propertyType,"Date"))
                        cv.put(underlineName,((Date)getter.invoke(bean)).getTime());
                    else if (StringUtils.in(propertyType,"StringBuffer"))
                        cv.put(underlineName,((StringBuffer)getter.invoke(bean)).toString());
                    else
                        throw new DaoException("Unsupport type ["+propertyType+"] in bean["+bean.getClass().getName()+"] ,property name is "+name);
                } catch (Exception e){
                    throw new DaoException(e);
                }
            }
        });
        return cv;
    }

    private String fillArgs(String sql, Object...args){
        StringBuffer buf = new StringBuffer(sql);
        int i = 0;
        while (true){
            int index = buf.indexOf("?");
            if (index==-1)break;
            buf.replace(index,index+1,toStr(args[i++]));
        }
        return buf.toString();
    }

    //变成SQL语句中的字符串 比如String类型要加引号
    private String toStr(Object arg){
        String type = arg.getClass().getSimpleName();
        String retStr;
        if (StringUtils.in(type,"String","StringBuffer"))
            retStr = "\""+arg.toString()+"\"";
        else if (StringUtils.in(type,"Date"))
            retStr = String.valueOf(((Date)arg).getTime());
        else if (StringUtils.in(type,"Boolean","boolean"))
            retStr = String.valueOf((Boolean)arg?1:0);
        else
            retStr = String.valueOf(arg);
        return retStr;
    }

}
